package com.guozinhs.auto.frame;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.guozinhs.auto.core.*;
import com.guozinhs.auto.data.AgConstant;
import com.guozinhs.auto.data.AgData;
import com.guozinhs.auto.data.AgDataManager;
import com.guozinhs.auto.frame.listener.FrameKeyListener;
import com.guozinhs.auto.frame.listener.NodeSelectedEventHandler;
import com.guozinhs.auto.frame.listener.NodeSelectedListener;
import com.guozinhs.auto.log.Logger;
import com.guozinhs.auto.util.get.Get;
import com.guozinhs.auto.util.get.TypeFunction;
import com.guozinhs.auto.util.image.ImageUtils;
import lombok.SneakyThrows;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * AutoGame操作窗口
 *
 * @Author: GuoZi
 */
public class AutoGameFrame extends JFrame implements ActionListener, NodeSelectedEventHandler {

    private Logger log;
    private AgData data;
    private AgDataManager dataManager;
    private ExecState execState;
    private Map<String, Component> componentMap;

    /**
     * 记录复制的节点id, 用于节点复制粘贴
     */
    private Long copyId;
    /**
     * 记录当前选中的节点, 用于高亮显示
     */
    private JComponent highlightNode;
    /**
     * 记录系统配置组件, 用于高亮显示
     */
    private JLabel systemConfig;

    public static final int MAX_WIDTH_IMAGE_PREVIEW = 400;
    public static final int MAX_HEIGHT_IMAGE_PREVIEW = 240;

    public static final Font DEFAULT_FONT = new Font("宋体", Font.PLAIN, 18);
    public static final Rectangle DEFAULT_AREA = new Rectangle(100, 100, 1130, 720);
    public static final LineBorder DEFAULT_BORDER_NORMAL = new LineBorder(new Color(187, 187, 187), 1);
    public static final LineBorder DEFAULT_BORDER_HIGHLIGHT = new LineBorder(new Color(255, 103, 103), 2);

    public static void main(String[] args) {
        AutoGameFrame frame = new AutoGameFrame();
        frame.init();
        frame.setVisible(true);
    }

    @SneakyThrows
    private void init() {
        this.componentMap = new HashMap<>(64);
        this.dataManager = new AgDataManager(AgConstant.PATH_DATA);
        this.data = this.dataManager.getData();
        this.log = new Logger(this.componentMap);
        this.execState = new AutoGameExecutor(this.log, this.dataManager, this.componentMap);
        StateNode selectedState = this.data.getSelectedNode();
        // 窗口样式与当前系统保持一致
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        // 字体
        this.setFont(DEFAULT_FONT);
        // 布局方式
        this.setLayout(new BorderLayout());
        // 窗口标题
        this.setTitle(AgConstant.TITLE);
        // 是否置顶窗口
        this.setAlwaysOnTop(false);
        // 窗口初始位置, 大小
        this.setBounds(DEFAULT_AREA);
        // 窗口关闭后操作: 退出程序
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 添加键盘按键监听器
        Toolkit.getDefaultToolkit().addAWTEventListener(new FrameKeyListener(this), AWTEvent.KEY_EVENT_MASK);
        // 创建各界面组件
        this.initBanner();
        this.initLogArea();
        this.initNodeList();
        this.initEditArea(selectedState);
        // 程序Icon
        try {
            this.setIconImage(ImageIO.read(new File(AgConstant.PATH_ICON)));
        } catch (IOException e) {
            log.info("未发现icon文件, 使用默认图标");
        }
    }

    /**
     * 初始化导航栏
     */
    private void initBanner() {
        JPanel banner = new JPanel();
        banner.setBackground(new Color(255, 255, 255));
        banner.setBorder(new LineBorder(new Color(187, 187, 187)));
        banner.setLayout(new FlowLayout(FlowLayout.LEFT));
        this.setSize(banner, 640, 36);
        this.componentMap.put(AgConstant.KEY_BANNER, banner);
        this.resetBanner(null);
        this.add(banner, BorderLayout.NORTH);
    }

    /**
     * 重置导航栏
     *
     * @param node 当前节点(为null时认为在首页)
     */
    private void resetBanner(StateNode node) {
        JPanel banner = (JPanel) this.componentMap.get(AgConstant.KEY_BANNER);
        banner.removeAll();
        // 系统配置按钮
        this.systemConfig = this.createText("系统配置");
        this.systemConfig.addMouseListener(new NodeSelectedListener(AgConstant.ID_SYSTEM_CONFIG, false, false, this));
        if (AgConstant.NODE_SYSTEM_CONFIG.equals(this.data.getSelectedNode())) {
            // 高亮显示
            this.highlight(AgConstant.ID_SYSTEM_CONFIG);
        }
        // 回到首页按钮
        JLabel main = this.createText("首页");
        main.addMouseListener(new NodeSelectedListener(null, true, false, this));
        banner.add(this.systemConfig);
        banner.add(this.createText("|"));
        banner.add(main);
        if (Objects.isNull(node)) {
            return;
        }
        List<StateNode> stateList = this.dataManager.getParents(node.getId());
        if (ObjectUtil.isEmpty(stateList)) {
            return;
        }
        banner.add(this.createText(File.separator));
        boolean first = true;
        for (StateNode stateNode : stateList) {
            if (first) {
                first = false;
            } else {
                banner.add(this.createText(File.separator));
            }
            JLabel text = this.createText(stateNode.getName());
            text.addMouseListener(new NodeSelectedListener(stateNode.getId(), true, false, this));
            banner.add(text);
        }
    }

    /**
     * 初始化日志区
     */
    private void initLogArea() {
        JPanel southPanel = new JPanel();
        southPanel.setLayout(new BorderLayout());
        // 日志显示区
        JTextArea textArea = new JTextArea("日志输出......");
        this.componentMap.put(AgConstant.KEY_LOG_AREA, textArea);
        textArea.setFont(DEFAULT_FONT);
        textArea.setLineWrap(true);
        textArea.setEditable(false);
        JScrollPane scrollPane = this.createScrollPane(textArea);
        this.setSize(scrollPane, 640, 64);
        // 启动, 停止按钮
        JPanel buttonPanel = new JPanel();
        this.setSize(buttonPanel, 200, 64);
        buttonPanel.setLayout(new GridLayout());
        JButton startButton = this.createButton("启动", ActType.run);
        JButton endButton = this.createButton("停止", ActType.stop);
        endButton.setEnabled(false);
        this.componentMap.put(AgConstant.KEY_BUTTON_RUN, startButton);
        this.componentMap.put(AgConstant.KEY_BUTTON_STOP, endButton);
        buttonPanel.add(startButton);
        buttonPanel.add(endButton);

        southPanel.add(scrollPane, BorderLayout.CENTER);
        southPanel.add(buttonPanel, BorderLayout.EAST);
        this.add(southPanel, BorderLayout.SOUTH);
    }

    /**
     * 初始化节点列表
     */
    private void initNodeList() {
        JPanel panel = new JPanel();
        panel.setBackground(new Color(242, 242, 242));
        panel.setLayout(null);
        this.componentMap.put(AgConstant.KEY_NODE_LIST, panel);
        this.resetNodeList(null);
        // 添加滚动组件, 高度为自适应
        JScrollPane scrollPane = this.createScrollPane(panel);
        this.setSize(scrollPane, 240, 0);
        this.add(scrollPane, BorderLayout.WEST);
    }

    /**
     * 重置节点列表
     *
     * @param node 当前节点(为null时认为在首页)
     */
    private void resetNodeList(StateNode node) {
        JPanel panel = (JPanel) this.componentMap.get(AgConstant.KEY_NODE_LIST);
        panel.removeAll();
        int totalHeight = 0;
        if (Objects.isNull(node)) {
            totalHeight = this.initRootNode(panel, totalHeight);
        } else {
            totalHeight = this.initParentNode(panel, totalHeight, node);
            totalHeight = this.initPreCheckNode(panel, totalHeight + 8, node);
            totalHeight = this.initPostCheckNode(panel, totalHeight + 8, node);
            totalHeight = this.initNextNodes(panel, totalHeight + 8, node);
        }
        // 设置节点列表总高度
        this.setSize(panel, 200, totalHeight);
    }

    /**
     * 初始化根节点列表
     *
     * @param parentPanel 父级组件
     * @param height      当前组件高度
     * @return 初始化之后的组件高度
     */
    private int initRootNode(JPanel parentPanel, int height) {
        int totalHeight = height;
        int buttonWidth = 32;
        int buttonHeight = 32;
        List<StateNode> rootStateList = this.dataManager.getChildren(null, StateType.normal);
        for (StateNode root : rootStateList) {
            Long id = root.getId();
            // 上边距
            totalHeight += 6;
            // 节点
            JPanel nodePanel = this.createClickableNode(root.getName(), id, true);
            nodePanel.setBounds(-1, totalHeight, 240 - buttonWidth, buttonHeight);
            nodePanel.setBackground(new Color(245, 255, 240));
            this.checkSelected(id, nodePanel);
            parentPanel.add(nodePanel);
            // 定位配置按钮
            StateNode locationNode = this.dataManager.getLocationNode(id);
            Long locationNodeId = locationNode.getId();
            JPanel locationPanel = this.createClickableNode("⊕", locationNodeId, false);
            locationPanel.setBounds(-1 + nodePanel.getWidth(), totalHeight, buttonWidth, buttonHeight);
            locationPanel.setBackground(new Color(245, 255, 240));
            this.checkSelected(locationNodeId, locationPanel);
            parentPanel.add(locationPanel);
            // 修改当前高度
            totalHeight += buttonHeight;
            this.componentMap.put(AgConstant.KEY_PREFIX_NODE + id, nodePanel);
            this.componentMap.put(AgConstant.KEY_PREFIX_NODE + locationNodeId, locationPanel);
        }
        JPanel buttonPanel = new JPanel();
        buttonPanel.setBounds(0, totalHeight, 240, 48);
        totalHeight += 48;
        buttonPanel.setLayout(new GridLayout());
        JButton startButton = this.createButton("新增", ActType.createRootState);
        JButton endButton = this.createButton("粘贴", ActType.pasteRootState);
        buttonPanel.add(startButton);
        buttonPanel.add(endButton);
        parentPanel.add(buttonPanel);
        return totalHeight;
    }

    /**
     * 校验该节点是否被选中
     *
     * @param id        节点id
     * @param nodePanel 节点组件
     */
    private void checkSelected(Long id, JPanel nodePanel) {
        boolean isSelected = this.dataManager.isSelected(id);
        if (isSelected) {
            this.highlightNode = nodePanel;
            nodePanel.setBorder(DEFAULT_BORDER_HIGHLIGHT);
        }
    }

    /**
     * 初始化当前节点
     *
     * @param parentPanel 父级组件
     * @param height      当前组件高度
     * @return 初始化之后的组件高度
     */
    private int initParentNode(JPanel parentPanel, int height, StateNode node) {
        int totalHeight = height;
        int buttonHeight = 32;
        JPanel nodePanel = this.createClickableNode(node.getName(), node.getId(), true);
        nodePanel.setBounds(0, totalHeight, 238, buttonHeight);
        nodePanel.setBackground(new Color(255, 248, 240));
        boolean isSelected = this.dataManager.isSelected(node.getId());
        if (isSelected) {
            this.highlightNode = nodePanel;
            nodePanel.setBorder(DEFAULT_BORDER_HIGHLIGHT);
        }
        totalHeight += buttonHeight;
        parentPanel.add(nodePanel);
        this.componentMap.put(AgConstant.KEY_PREFIX_NODE + node.getId(), nodePanel);
        return totalHeight;
    }

    /**
     * 创建子节点列表
     *
     * @param parentPanel 父级组件
     * @param height      当前组件高度
     * @param node        父节点
     * @param stateType   子节点类型
     * @param category    该列表的分类名称
     * @param create      创建按钮事件类型
     * @param copy        复制按钮事件类型
     * @return 创建之后的组件高度
     */
    private int initNode(JPanel parentPanel, int height, StateNode node, StateType stateType, String category, ActType create, ActType copy) {
        int totalHeight = height;
        int buttonHeight = 32;
        if (StrUtil.isNotBlank(category)) {
            JLabel text = this.createText(category);
            text.setBounds(8, totalHeight, 238, buttonHeight);
            totalHeight += buttonHeight;
            parentPanel.add(text);
        }
        List<StateNode> children = this.dataManager.getChildren(node.getId(), stateType);
        for (StateNode child : children) {
            JPanel nextNode = this.createClickableNode(child.getName(), child.getId(), true);
            nextNode.setBounds(-1, totalHeight, 240, buttonHeight);
            nextNode.setBackground(new Color(245, 255, 240));
            boolean isSelected = this.dataManager.isSelected(child.getId());
            if (isSelected) {
                this.highlightNode = nextNode;
                nextNode.setBorder(DEFAULT_BORDER_HIGHLIGHT);
            }
            totalHeight += buttonHeight;
            parentPanel.add(nextNode);
            this.componentMap.put(AgConstant.KEY_PREFIX_NODE + child.getId(), nextNode);
        }
        JPanel buttonPanel = new JPanel();
        buttonPanel.setBounds(0, totalHeight, 240, 48);
        totalHeight += 48;
        buttonPanel.setLayout(new GridLayout());
        JButton startButton = this.createButton("新增", create);
        JButton endButton = this.createButton("粘贴", copy);
        buttonPanel.add(startButton);
        buttonPanel.add(endButton);
        parentPanel.add(buttonPanel);
        return totalHeight;
    }

    /**
     * 初始化前置校验节点
     *
     * @param parentPanel 父级组件
     * @param height      当前组件高度
     * @return 初始化之后的组件高度
     */
    private int initPreCheckNode(JPanel parentPanel, int height, StateNode node) {
        return this.initNode(parentPanel, height, node, StateType.preCheck, "前置校验节点", ActType.createPreCheckState, ActType.pastePreCheckState);
    }

    /**
     * 初始化后置校验节点
     *
     * @param parentPanel 父级组件
     * @param height      当前组件高度
     * @return 初始化之后的组件高度
     */
    private int initPostCheckNode(JPanel parentPanel, int height, StateNode node) {
        return this.initNode(parentPanel, height, node, StateType.postCheck, "后置校验节点", ActType.createPostCheckState, ActType.pastePostCheckState);
    }

    /**
     * 初始化后续节点
     *
     * @param parentPanel 父级组件
     * @param height      当前组件高度
     * @return 初始化之后的组件高度
     */
    private int initNextNodes(JPanel parentPanel, int height, StateNode node) {
        return this.initNode(parentPanel, height, node, StateType.normal, "后续节点", ActType.createNextState, ActType.pasteNextState);
    }

    /**
     * 初始化属性编辑区
     */
    private void initEditArea(StateNode node) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        // 按钮区
        JPanel buttonPanel = new JPanel();
        this.setSize(buttonPanel, 0, 40);
        buttonPanel.add(this.createButton("上移", ActType.moveUp, AgConstant.KEY_PREFIX_EDIT_BUTTON));
        buttonPanel.add(this.createButton("下移", ActType.moveDown, AgConstant.KEY_PREFIX_EDIT_BUTTON));
        buttonPanel.add(this.createButton("保存", ActType.updateState, AgConstant.KEY_PREFIX_EDIT_BUTTON));
        buttonPanel.add(this.createButton("删除", ActType.removeState, AgConstant.KEY_PREFIX_EDIT_BUTTON));
        buttonPanel.add(this.createButton("复制节点", ActType.copyState, AgConstant.KEY_PREFIX_EDIT_BUTTON));
        panel.add(buttonPanel, BorderLayout.SOUTH);
        // 节点属性
        JPanel nodePropPanel = new JPanel();
        nodePropPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        this.initNodePropItems(nodePropPanel);
        this.resetEditProp(node);
        panel.add(nodePropPanel, BorderLayout.CENTER);
        this.add(panel, BorderLayout.CENTER);
    }

    /**
     * 初始化属性编辑项
     *
     * @param parent 父级组件
     */
    private void initNodePropItems(Container parent) {
        int width = 420;
        // 全局配置
        this.createEditItemPanel(parent, "保存图像日志", this.createEditCheckBox(AgData::getEnableImageLog, ""));
        this.createEditItemPanel(parent, "启动时最小化", this.createEditCheckBox(AgData::getMinimizeWhenStart, ""));
        this.createEditItemPanel(parent, "最小点击间隔", this.createEditTextInput(AgData::getMinClickInterval));
        // 节点属性
        this.createEditItemPanel(parent, "节点名称", this.createEditTextInput(StateNode::getName));
        this.createEditItemPanel(parent, "窗口宽度", this.createEditTextInput(StateNode::getWidth));
        this.createEditItemPanel(parent, "窗口高度", this.createEditTextInput(StateNode::getHeight));
        this.createEditItemPanel(parent, "定位偏移(X)", this.createEditTextInput(StateNode::getOffsetX));
        this.createEditItemPanel(parent, "定位偏移(Y)", this.createEditTextInput(StateNode::getOffsetY));
        this.createEditItemPanel(parent, "鼠标平滑移动", this.createEditCheckBox(StateNode::getSmoothMove, ""));
        this.createEditItemPanel(parent, "平滑速度", this.createEditTextInput(StateNode::getSmoothMoveSpeed));
        this.createEditItemPanel(parent, "鼠标随机偏移", this.createEditCheckBox(StateNode::getRandomMove, ""));
        this.createEditItemPanel(parent, "偏移范围", this.createEditTextInput(StateNode::getRandomMoveRadius));
        this.createEditItemPanel(parent, "图片匹配度(%)", this.createEditTextInput(StateNode::getMatchPercent));
        this.createEditItemPanel(parent, "循环执行", this.createEditCheckBox(StateNode::getIsCycle, ""));
        this.createEditItemPanel(parent, "失败后重试", this.createEditCheckBox(StateNode::getIsForced, ""));
        this.createEditItemPanel(parent, "匹配前延迟(ms)", this.createEditTextInput(StateNode::getPreDelay));
        this.createEditItemPanel(parent, "执行后延迟(ms)", this.createEditTextInput(StateNode::getPostDelay));
        this.createEditItemPanel(parent, "前置校验", this.createEditCheckBox(StateNode::getPreCheckSkip, "匹配成功时跳过当前节点"));
        this.createEditItemPanel(parent, "后置校验", this.createEditCheckBox(StateNode::getPostCheckSkip, "匹配成功时跳过后续节点"));
        this.createEditItemPanel(parent, "执行脚本", this.createEditTextArea(StateNode::getScript), width, 120);
        // 图片设置区
        JPanel imageArea = new JPanel();
        this.setSize(imageArea, width, 300);
        imageArea.setLayout(new FlowLayout(FlowLayout.LEFT));
        imageArea.add(this.createEditText("匹配图片"));
        int buttonWidth = 70;
        int buttonHeight = 32;
        imageArea.add(this.createButton("设置", ActType.updateImage, buttonWidth, buttonHeight));
        imageArea.add(this.createButton("清空", ActType.clearImage, buttonWidth, buttonHeight));
        imageArea.add(this.createButton("复制", ActType.getImage, buttonWidth, buttonHeight));
        // 图片预览区
        JPanel imgPreviewPanel = new JPanel();
        imgPreviewPanel.setLayout(null);
        imgPreviewPanel.setBackground(Color.WHITE);
        this.setSize(imgPreviewPanel, MAX_WIDTH_IMAGE_PREVIEW + 2, MAX_HEIGHT_IMAGE_PREVIEW + 2);
        ImagePanel imagePanel = new ImagePanel();
        imagePanel.setBorder(new LineBorder(new Color(127, 127, 127), 2));
        this.componentMap.put(AgConstant.KEY_PREFIX_EDIT_AREA + "imageView", imagePanel);
        this.componentMap.put(AgConstant.KEY_PREFIX_PROP_ITEM + "imageView", imageArea);
        imgPreviewPanel.add(imagePanel);
        imageArea.add(imgPreviewPanel);
        parent.add(imageArea);
    }

    /**
     * 创建属性编辑项
     *
     * @param parent   父级组件
     * @param desc     描述文字
     * @param editArea 编辑区组件
     * @return 创建后的编辑组件
     */
    private JPanel createEditItemPanel(Container parent, String desc, Component editArea) {
        return this.createEditItemPanel(parent, desc, editArea, 420, 45);
    }

    /**
     * 创建属性编辑项
     *
     * @param parent   父级组件
     * @param desc     描述文字
     * @param editArea 编辑区组件
     * @param width    编辑项宽度
     * @param height   编辑项高度
     * @return 创建后的编辑组件
     */
    private JPanel createEditItemPanel(Container parent, String desc, Component editArea, int width, int height) {
        JPanel panel = new JPanel();
        this.setSize(panel, width, height);
        panel.setLayout(new FlowLayout(FlowLayout.LEFT));
        panel.add(this.createEditText(desc));
        panel.add(editArea);
        parent.add(panel);
        this.componentMap.put(AgConstant.KEY_PREFIX_PROP_ITEM + editArea.getName(), panel);
        return panel;
    }

    /**
     * 重置节点的属性编辑区
     *
     * @param node 节点信息
     */
    private void resetEditProp(StateNode node) {
        // 隐藏编辑组件和按钮
        this.setVisible(false, AgConstant.KEY_PREFIX_PROP_ITEM, AgConstant.KEY_PREFIX_EDIT_BUTTON);
        if (Objects.isNull(node)) {
            // 节点为空时, 不显示配置项
            return;
        }
        if (node instanceof SystemConfigNode) {
            // 显示系统配置项
            this.resetSystemProp(this.data);
            return;
        }
        StateType stateType = node.getStateType();
        if (StateType.location.equals(stateType)) {
            // Long
            this.setEditProp(node, StateNode::getWidth, 1920L);
            this.setEditProp(node, StateNode::getHeight, 1080L);
            this.setEditProp(node, StateNode::getOffsetX, 0L);
            this.setEditProp(node, StateNode::getOffsetY, 0L);
            this.setEditProp(node, StateNode::getSmoothMove, false);
            this.setEditProp(node, StateNode::getRandomMove, false);
            // Boolean
            this.setEditProp(node, StateNode::getSmoothMoveSpeed, 0);
            this.setEditProp(node, StateNode::getRandomMoveRadius, 0);
            // 显示按钮
            this.setVisible(true, AgConstant.KEY_PREFIX_EDIT_BUTTON + ActType.updateState);
        } else {
            // String
            this.setEditProp(node, StateNode::getName, "");
            this.setEditProp(node, StateNode::getScript, "");
            // Long
            this.setEditProp(node, StateNode::getPreDelay, 0L);
            this.setEditProp(node, StateNode::getPostDelay, 0L);
            // Boolean
            this.setEditProp(node, StateNode::getIsCycle, false);
            this.setEditProp(node, StateNode::getIsForced, false);
            this.setEditProp(node, StateNode::getPreCheckSkip, false);
            this.setEditProp(node, StateNode::getPostCheckSkip, false);
            // 显示按钮
            this.setVisible(true, AgConstant.KEY_PREFIX_EDIT_BUTTON);
        }
        // 图片匹配度
        this.setEditProp(node, StateNode::getMatchPercent, 0.0);
        // 图片编辑区
        this.resetEditImage(node);
    }

    /**
     * 重置系统配置数据
     *
     * @param data 系统配置
     */
    private void resetSystemProp(AgData data) {
        if (Objects.isNull(data)) {
            log.info("数据异常, 请重启!!!");
            return;
        }
        // Boolean
        this.setEditProp(data, AgData::getEnableImageLog, true);
        this.setEditProp(data, AgData::getMinimizeWhenStart, false);
        this.setEditProp(data, AgData::getMinClickInterval, 0L);
        // 显示按钮
        this.setVisible(true, AgConstant.KEY_PREFIX_EDIT_BUTTON + ActType.updateState);
    }

    /**
     * 设置组件显隐
     *
     * @param visible    是否可见
     * @param prefixKeys 组件标识前缀
     */
    private void setVisible(boolean visible, String... prefixKeys) {
        if (Objects.isNull(prefixKeys) || prefixKeys.length == 0) {
            return;
        }
        for (Map.Entry<String, Component> entry : this.componentMap.entrySet()) {
            String key = entry.getKey();
            Component component = entry.getValue();
            for (String prefixKey : prefixKeys) {
                if (key.startsWith(prefixKey)) {
                    component.setVisible(visible);
                }
            }
        }
    }

    /**
     * 重置图片编辑区
     *
     * @param node 节点信息
     */
    private void resetEditImage(StateNode node) {
        ImagePanel imageView = (ImagePanel) this.componentMap.get(AgConstant.KEY_PREFIX_EDIT_AREA + "imageView");
        this.componentMap.get(AgConstant.KEY_PREFIX_PROP_ITEM + "imageView").setVisible(true);
        imageView.getParent().getParent().setVisible(true);
        if (Objects.isNull(node)) {
            imageView.setVisible(false);
            return;
        }
        String imagePath = this.dataManager.getImagePath(node.getId());
        Image image = ImageUtils.read(imagePath);
        imageView.setImage(image);
        // 调整预览图片的尺寸
        if (Objects.isNull(image)) {
            imageView.setVisible(false);
        } else {
            imageView.setVisible(true);
            int width = image.getWidth(null) * 10;
            int height = image.getHeight(null) * 10;
            double scale = width * 1.0 / height;
            if (width > MAX_WIDTH_IMAGE_PREVIEW) {
                width = MAX_WIDTH_IMAGE_PREVIEW;
                height = (int) (width / scale);
            }
            if (height > MAX_HEIGHT_IMAGE_PREVIEW) {
                height = MAX_HEIGHT_IMAGE_PREVIEW;
                width = (int) (height * scale);
            }
            imageView.setBounds((MAX_WIDTH_IMAGE_PREVIEW - width) >> 1, (MAX_HEIGHT_IMAGE_PREVIEW - height) >> 1, width, height);
        }
    }

    /**
     * 获取编辑区的系统配置
     *
     * @return 系统配置
     */
    private AgData getEditSystemProp() {
        AgData data = new AgData();
        // Boolean
        data.setEnableImageLog(this.getEditProp(AgData::getEnableImageLog));
        data.setMinimizeWhenStart(this.getEditProp(AgData::getMinimizeWhenStart));
        data.setMinClickInterval(this.getEditProp(AgData::getMinClickInterval));
        return data;
    }

    /**
     * 获取编辑区的节点数据
     *
     * @return 节点数据
     */
    private StateNode getEditNodeProp() {
        StateNode node = new StateNode();
        // String
        node.setName(this.getEditProp(StateNode::getName));
        node.setScript(this.getEditProp(StateNode::getScript));
        // Long
        node.setPreDelay(this.getEditProp(StateNode::getPreDelay));
        node.setPostDelay(this.getEditProp(StateNode::getPostDelay));
        node.setWidth(this.getEditProp(StateNode::getWidth));
        node.setHeight(this.getEditProp(StateNode::getHeight));
        node.setOffsetX(this.getEditProp(StateNode::getOffsetX));
        node.setOffsetY(this.getEditProp(StateNode::getOffsetY));
        node.setSmoothMoveSpeed(this.getEditProp(StateNode::getSmoothMoveSpeed));
        node.setRandomMoveRadius(this.getEditProp(StateNode::getRandomMoveRadius));
        // Double
        node.setMatchPercent(this.getEditProp(StateNode::getMatchPercent));
        // Boolean
        node.setIsCycle(this.getEditProp(StateNode::getIsCycle));
        node.setIsForced(this.getEditProp(StateNode::getIsForced));
        node.setPreCheckSkip(this.getEditProp(StateNode::getPreCheckSkip));
        node.setPostCheckSkip(this.getEditProp(StateNode::getPostCheckSkip));
        node.setSmoothMove(this.getEditProp(StateNode::getSmoothMove));
        node.setRandomMove(this.getEditProp(StateNode::getRandomMove));
        return node;
    }

    /**
     * 获取编辑区的单个属性值
     *
     * @param getter 该属性的get方法
     * @param <T>    属性类型
     * @return 属性值
     */
    private <R, T> T getEditProp(TypeFunction<R, T> getter) {
        Class<T> tClass = Get.aClass(getter);
        String propName = Get.name(getter);
        Component component = this.componentMap.get(AgConstant.KEY_PREFIX_EDIT_AREA + propName);
        if (Boolean.class.equals(tClass)) {
            return (T) Boolean.valueOf(((AbstractButton) component).isSelected());
        }
        String text = ((JTextComponent) component).getText();
        if (String.class.equals(tClass)) {
            return (T) text;
        }
        if (StrUtil.isBlank(text)) {
            text = "0";
        }
        if (Integer.class.equals(tClass)) {
            try {
                return (T) Integer.valueOf(text);
            } catch (Exception e) {
                log.error("数字格式异常: " + text, e);
            }
        }
        if (Long.class.equals(tClass)) {
            try {
                return (T) Long.valueOf(text);
            } catch (Exception e) {
                log.error("数字格式异常: " + text, e);
            }
        }
        if (Double.class.equals(tClass)) {
            try {
                return (T) Double.valueOf(text);
            } catch (Exception e) {
                log.error("数字格式异常: " + text, e);
            }
        }
        return null;
    }

    /**
     * 修改编辑区的单个属性值
     *
     * @param node   节点数据
     * @param getter 该属性的get方法
     * @param <T>    属性类型
     */
    private <R, T> void setEditProp(R node, TypeFunction<R, T> getter, T defaultValue) {
        T data = null;
        if (Objects.nonNull(node)) {
            data = getter.apply(node);
        }
        if (Objects.isNull(data)) {
            data = defaultValue;
        }
        String propName = Get.name(getter);
        Component component = this.componentMap.get(AgConstant.KEY_PREFIX_EDIT_AREA + propName);
        this.setVisible(true, AgConstant.KEY_PREFIX_PROP_ITEM + propName);
        if (data instanceof Boolean) {
            ((AbstractButton) component).setSelected((Boolean) data);
            return;
        }
        String text = "";
        if (Objects.nonNull(data)) {
            text = data.toString();
        }
        ((JTextComponent) component).setText(text);
    }

    /**
     * 添加组件
     *
     * @param component 组件
     * @param key       组件标示
     */
    public void add(Component component, String key) {
        component.setFont(DEFAULT_FONT);
        super.add(component, key);
    }

    /**
     * 设置组件偏好尺寸
     *
     * @param component 组件
     * @param width     宽度
     * @param height    高度
     */
    private void setSize(JComponent component, int width, int height) {
        component.setPreferredSize(new Dimension(width, height));
    }

    /**
     * 创建按钮
     *
     * @param name    按钮名称
     * @param actType 按钮触发事件
     * @return 按钮组件
     */
    private JButton createButton(String name, ActType actType) {
        return this.createButton(name, actType, null);
    }

    /**
     * 创建按钮
     * <p>
     * 并根据标识前缀保存该按钮
     *
     * @param name      按钮名称
     * @param actType   按钮触发事件
     * @param prefixKey 组件标识前缀
     * @return 按钮组件
     */
    private JButton createButton(String name, ActType actType, String prefixKey) {
        JButton button = new JButton(name);
        button.setFont(DEFAULT_FONT);
        if (Objects.nonNull(actType)) {
            String command = actType.name();
            button.setActionCommand(command);
            button.addActionListener(this);
            if (StrUtil.isNotBlank(prefixKey)) {
                this.componentMap.put(prefixKey + command, button);
            }
        }
        button.setFocusable(false);
        return button;
    }

    /**
     * 创建按钮
     *
     * @param name    按钮名称
     * @param actType 按钮触发事件
     * @param width   按钮宽度
     * @param height  按钮高度
     * @return 按钮组件
     */
    private JButton createButton(String name, ActType actType, int width, int height) {
        JButton button = this.createButton(name, actType);
        this.setSize(button, width, height);
        return button;
    }

    /**
     * 创建文本
     *
     * @param text 文本内容
     * @return 文本组件
     */
    private JLabel createText(String text) {
        JLabel label = new JLabel(text);
        label.setFont(DEFAULT_FONT);
        return label;
    }

    /**
     * 创建可点击的节点
     *
     * @param nodeName             节点名称
     * @param nodeId               节点id
     * @param moveDirIfDoubleClick 双击时是否跳转目录
     * @return 节点组件
     */
    private JPanel createClickableNode(String nodeName, Long nodeId, boolean moveDirIfDoubleClick) {
        JPanel panel = new JPanel();
        panel.setBorder(new LineBorder(new Color(187, 187, 187), 1));
        panel.addMouseListener(new NodeSelectedListener(nodeId, false, moveDirIfDoubleClick, this));
        JLabel label = new JLabel(nodeName);
        label.setFont(DEFAULT_FONT);
        panel.add(label);
        return panel;
    }

    /**
     * 创建带滚动功能的组件
     *
     * @param component 子组件
     * @return 滚动组件
     */
    private JScrollPane createScrollPane(JComponent component) {
        JScrollPane scrollPane = new JScrollPane(component);
        JScrollBar scrollBar = new JScrollBar(JScrollBar.VERTICAL) {
            @Override
            public boolean isVisible() {
                return true;
            }
        };
        scrollBar.setUnitIncrement(40);
        scrollPane.setVerticalScrollBar(scrollBar);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
        return scrollPane;
    }

    /**
     * 创建属性编辑说明文本
     *
     * @param name 文本内容
     * @return 文本组件
     */
    private JPanel createEditText(String name) {
        JPanel panel = new JPanel();
        JLabel text = new JLabel(name);
        text.setFont(DEFAULT_FONT);
        this.setSize(panel, 130, 40);
        panel.setLayout(new GridLayout());
        panel.add(text);
        return panel;
    }

    /**
     * 创建属性编辑的单行文本输入框
     *
     * @param getter 节点属性
     * @return 文本输入组件
     */
    private <T, R> JPanel createEditTextInput(TypeFunction<T, R> getter) {
        JPanel panel = new JPanel();
        String name = Get.name(getter);
        JTextField textInput = new JTextField();
        textInput.setFont(DEFAULT_FONT);
        this.setSize(panel, 210, 40);
        panel.setLayout(new GridLayout());
        panel.add(textInput);
        panel.setName(name);
        this.componentMap.put(AgConstant.KEY_PREFIX_EDIT_AREA + name, textInput);
        return panel;
    }

    /**
     * 创建属性编辑的多行文本输入框
     *
     * @param getter 节点属性
     * @return 文本输入组件
     */
    private <T> JScrollPane createEditTextArea(TypeFunction<StateNode, T> getter) {
        String name = Get.name(getter);
        JTextArea scriptTextInput = new JTextArea("click");
        scriptTextInput.setFont(DEFAULT_FONT);
        scriptTextInput.setLineWrap(true);
        JScrollPane scrollPane = this.createScrollPane(scriptTextInput);
        this.setSize(scrollPane, 240, 110);
        scrollPane.setName(name);
        this.componentMap.put(AgConstant.KEY_PREFIX_EDIT_AREA + name, scriptTextInput);
        return scrollPane;
    }

    /**
     * 创建属性编辑的单选框
     *
     * @param getter 节点属性
     * @return 单选框组件
     */
    private <T, R> JPanel createEditCheckBox(TypeFunction<T, R> getter, String placeholder) {
        JPanel panel = new JPanel();
        String name = Get.name(getter);
        this.setSize(panel, 240, 40);
        panel.setLayout(new GridLayout());
        JCheckBox checkBox = new JCheckBox(placeholder);
        checkBox.setFont(DEFAULT_FONT);
        panel.add(checkBox);
        panel.setName(name);
        this.componentMap.put(AgConstant.KEY_PREFIX_EDIT_AREA + name, checkBox);
        return panel;
    }

    /**
     * 高亮显示组件
     *
     * @param id 选中的组件对应的节点id
     */
    private void highlight(Long id) {
        // 取消原高亮
        if (Objects.nonNull(this.highlightNode)) {
            if (this.highlightNode instanceof JLabel) {
                this.systemConfig.setForeground(Color.black);
            } else {
                this.highlightNode.setBorder(DEFAULT_BORDER_NORMAL);
            }
        }
        if (Objects.isNull(id)) {
            this.highlightNode = null;
            return;
        }
        JComponent highlightPanel;
        if (AgConstant.ID_SYSTEM_CONFIG.equals(id)) {
            highlightPanel = this.systemConfig;
            this.systemConfig.setForeground(new Color(200, 40, 40));
        } else {
            highlightPanel = (JPanel) this.componentMap.get(AgConstant.KEY_PREFIX_NODE + id);
            highlightPanel.setBorder(DEFAULT_BORDER_HIGHLIGHT);
        }
        this.highlightNode = highlightPanel;
    }

    /**
     * 选中节点
     *
     * @param selectedStateId 被选中的节点id
     */
    private void selectState(Long selectedStateId, boolean refreshAnyway) {
        StateNode selectedState = this.data.getSelectedNode();
        if (!refreshAnyway && this.isSameState(selectedState, selectedStateId)) {
            return;
        }
        StateNode targetNode;
        Long targetNodeId = null;
        if (AgConstant.ID_SYSTEM_CONFIG.equals(selectedStateId)) {
            // 进入系统配置界面
            targetNode = AgConstant.NODE_SYSTEM_CONFIG;
        } else if (Objects.isNull(selectedStateId)) {
            // 回到根目录
            List<StateNode> rootNodeList = this.dataManager.getChildren(null, StateType.normal);
            if (ObjectUtil.isEmpty(rootNodeList)) {
                targetNode = null;
            } else {
                targetNode = rootNodeList.get(0);
            }
        } else {
            targetNode = this.dataManager.getById(selectedStateId);
        }
        if (Objects.nonNull(targetNode)) {
            targetNodeId = targetNode.getId();
        }
        this.data.setSelectedNode(targetNode);
        this.resetEditProp(targetNode);
        this.highlight(targetNodeId);
        // 刷新界面
        this.refresh();
    }

    /**
     * 切换指定目录
     *
     * @param selectedStateId 被选中的节点id
     */
    private void moveDir(Long selectedStateId, boolean refreshAnyway) {
        StateNode currentNode = this.data.getParentNode();
        if (!refreshAnyway && this.isSameState(currentNode, selectedStateId)) {
            return;
        }
        StateNode targetState;
        if (Objects.isNull(selectedStateId)) {
            // 回到根目录
            targetState = null;
        } else {
            // 进入某目录
            targetState = this.dataManager.getById(selectedStateId);
        }
        this.data.setParentNode(targetState);
        this.resetBanner(targetState);
        this.resetNodeList(targetState);
        // 刷新界面
        this.refresh();
    }

    /**
     * 比较是否为同一节点
     *
     * @param stateNode   节点信息
     * @param stateNodeId 节点id信息
     * @return 比较结果
     */
    private boolean isSameState(StateNode stateNode, Long stateNodeId) {
        Long id;
        if (Objects.isNull(stateNode)) {
            id = null;
        } else {
            id = stateNode.getId();
        }
        return Objects.equals(id, stateNodeId);
    }

    /**
     * 重新渲染界面
     */
    private void refresh() {
        this.repaint();
        this.validate();
    }

    /**
     * 新建节点
     *
     * @param parentNode 上级节点
     * @param stateType  新建的节点类型
     */
    private void createNode(StateNode parentNode, StateType stateType) {
        Long parentId = null;
        if (Objects.nonNull(parentNode)) {
            parentId = parentNode.getId();
        }
        StateNode node = this.dataManager.create(parentId, stateType);
        Long newId = node.getId();
        this.resetNodeList(parentNode);
        this.selectState(newId, true);
    }

    /**
     * 复制节点
     *
     * @param parentNode 上级节点
     * @param stateType  复制后的的节点类型
     */
    private void copyNode(StateNode parentNode, StateType stateType) {
        if (Objects.isNull(this.copyId)) {
            log.info("请先复制一个节点后再操作");
            return;
        }
        Long parentId = null;
        if (Objects.nonNull(parentNode)) {
            parentId = parentNode.getId();
        }
        StateNode node = this.dataManager.copy(this.copyId, parentId, stateType);
        Long newId = node.getId();
        this.resetNodeList(parentNode);
        this.selectState(newId, true);
    }

    /**
     * 组件事件统一入口
     *
     * @param event 事件
     */
    @Override
    public void actionPerformed(ActionEvent event) {
        String command = event.getActionCommand();
        ActType actType = ActType.getByName(command);
        if (Objects.isNull(actType)) {
            log.info("未知的操作: {}", command);
            return;
        }
        log.info(actType.getDesc());
        Long rootId = null;
        Long selectedId = null;
        StateType rootType = null;
        StateType selectedType = null;
        StateNode rootNode = this.data.getParentNode();
        StateNode selectedNode = this.data.getSelectedNode();
        // 判断当前是否为系统配置界面
        boolean isSystemConfig = false;
        if (AgConstant.NODE_SYSTEM_CONFIG.equals(selectedNode)) {
            isSystemConfig = true;
            selectedNode = null;
        }
        if (Objects.nonNull(rootNode)) {
            rootId = rootNode.getId();
            rootType = rootNode.getStateType();
        }
        if (Objects.nonNull(selectedNode)) {
            selectedId = selectedNode.getId();
            selectedType = selectedNode.getStateType();
        }
        // TODO 优化代码结构
        switch (actType) {
            case run: {
                if (isSystemConfig) {
                    log.info("未指定执行节点");
                    return;
                }
                // 启动时窗口最小化
                boolean minimize = Boolean.TRUE.equals(this.data.getMinimizeWhenStart());
                if (minimize) {
                    this.setExtendedState(Frame.ICONIFIED);
                }
                this.execState.run(selectedId);
                return;
            }
            case suspend: {
                this.execState.suspend();
                return;
            }
            case continued: {
                this.execState.continued();
                return;
            }
            case stop: {
                this.execState.shutdown();
                return;
            }
            case createRootState: {
                this.createNode(null, StateType.normal);
                break;
            }
            case createNextState: {
                this.createNode(rootNode, StateType.normal);
                break;
            }
            case createPreCheckState: {
                this.createNode(rootNode, StateType.preCheck);
                break;
            }
            case createPostCheckState: {
                this.createNode(rootNode, StateType.postCheck);
                break;
            }
            case pasteRootState: {
                this.copyNode(null, StateType.normal);
                break;
            }
            case pasteNextState: {
                this.copyNode(rootNode, StateType.normal);
                break;
            }
            case pastePreCheckState: {
                this.copyNode(rootNode, StateType.preCheck);
                break;
            }
            case pastePostCheckState: {
                this.copyNode(rootNode, StateType.postCheck);
                break;
            }
            case moveUp: {
                if (isSystemConfig) {
                    log.info("不能移动系统配置");
                    return;
                }
                if (StateType.location.equals(selectedType)) {
                    log.info("不能移动定位配置");
                    return;
                }
                if (Objects.equals(rootId, selectedId)) {
                    // 不能直接移动父节点位置
                    log.info("不能移动父节点");
                    return;
                }
                this.dataManager.swapSort(selectedNode, -1);
                this.resetNodeList(rootNode);
                this.refresh();
                break;
            }
            case moveDown: {
                if (isSystemConfig) {
                    log.info("不能移动系统配置");
                    return;
                }
                if (StateType.location.equals(selectedType)) {
                    log.info("不能移动定位配置");
                    return;
                }
                if (Objects.equals(rootId, selectedId)) {
                    // 不能直接移动父节点位置
                    log.info("不能移动父节点");
                    return;
                }
                this.dataManager.swapSort(selectedNode, 1);
                this.resetNodeList(rootNode);
                this.refresh();
                break;
            }
            case updateState: {
                // 修改系统配置
                if (isSystemConfig) {
                    AgData agData = this.getEditSystemProp();
                    this.data.setEnableImageLog(agData.getEnableImageLog());
                    this.data.setMinimizeWhenStart(agData.getMinimizeWhenStart());
                    this.data.setMinClickInterval(agData.getMinClickInterval());
                    break;
                }
                if (Objects.isNull(selectedNode)) {
                    log.info("未选中节点, 保存失败");
                    return;
                }
                CopyOptions copyOptions = CopyOptions.create()
                        .setIgnoreProperties(Get.name(StateNode::getSort))
                        .ignoreNullValue();
                BeanUtil.copyProperties(this.getEditNodeProp(), selectedNode, copyOptions);
                // 重新渲染导航栏与节点列表
                this.moveDir(rootId, true);
                break;
            }
            case removeState: {
                if (isSystemConfig) {
                    log.info("不能删除系统配置");
                    return;
                }
                if (Objects.isNull(selectedId)) {
                    log.info("未选中节点, 无法删除");
                    return;
                }
                if (StateType.location.equals(selectedType)) {
                    log.info("不能删除定位配置");
                    return;
                }
                int index = selectedNode.getSort();
                this.dataManager.deleteById(selectedId);
                if (selectedId.equals(rootId)) {
                    // 删除当前节点后, 回到上级目录
                    this.moveDir(rootNode.getParentId(), true);
                    this.selectState(rootNode.getParentId(), true);
                } else {
                    // 删除子节点, 仅重新渲染节点列表
                    Long newSelectedId = rootId;
                    StateType newSelectedType = selectedType;
                    List<StateNode> children;
                    // 按节点列表顺序寻找默认选中的节点
                    while (newSelectedType != null) {
                        children = this.dataManager.getChildren(rootId, newSelectedType);
                        if (ObjectUtil.isEmpty(children)) {
                            newSelectedType = newSelectedType.getPreType();
                            index = Integer.MAX_VALUE;
                        } else {
                            if (index >= children.size()) {
                                index = children.size() - 1;
                            }
                            newSelectedId = children.get(index).getId();
                            break;
                        }
                    }
                    this.resetNodeList(rootNode);
                    this.selectState(newSelectedId, true);
                }
                break;
            }
            case copyState: {
                if (isSystemConfig) {
                    log.info("不能复制系统配置");
                    return;
                }
                if (Objects.isNull(selectedNode)) {
                    log.info("未选中节点, 无法复制");
                    return;
                }
                if (StateType.location.equals(selectedNode.getStateType())) {
                    log.info("不能复制定位配置");
                    return;
                }
                this.copyId = selectedId;
                return;
            }
            case updateImage: {
                Image image = ImageUtils.readFromClipboard();
                if (Objects.isNull(image)) {
                    log.info("读取剪切板失败, 请截图后再尝试");
                    return;
                }
                String imagePath = this.dataManager.getImagePath(selectedId);
                ImageUtils.write(imagePath, image);
                this.resetEditImage(selectedNode);
                this.refresh();
                return;
            }
            case clearImage: {
                String imagePath = this.dataManager.getImagePath(selectedId);
                FileUtil.del(imagePath);
                this.resetEditImage(selectedNode);
                this.refresh();
                return;
            }
            case getImage: {
                String imagePath = this.dataManager.getImagePath(selectedId);
                if (!FileUtil.exist(imagePath)) {
                    log.info("图像为空, 不能复制");
                    return;
                }
                ImageUtils.write2Clipboard(ImageUtils.read(imagePath));
                return;
            }
            default:
                return;
        }
        this.dataManager.saveProject();
    }

    @Override
    public void stateSelected(Long id, boolean moveDir) {
        this.selectState(id, false);
        if (moveDir) {
            this.moveDir(id, false);
        }
    }

}
